package controllers

import (
	"context"
	errors2 "errors"
	"fmt"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	mocks2 "gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset/mocks"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/lease"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/mocks"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/rest"
	controllerruntime "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sync"
	"testing"
)

func TestTriggerInstanceReconciler(t *testing.T) {
	ctx := context.Background()
	req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "test", Name: "test"}}
	mClient := mocks.NewClient(t)
	mClientWithErr := mocks.NewClient(t)
	mStatusWriter := mocks.NewSubResourceWriter(t)
	ti := v1alpha1.TriggerInstance{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "TestTI",
			Namespace: "Test",
			Labels: map[string]string{
				"test": "true",
			},
			Annotations: map[string]string{
				"test": "true",
			},
		},
		Spec: v1alpha1.TriggerInstanceSpec{},
	}
	tiList := v1alpha1.TriggerInstanceList{
		Items: []v1alpha1.TriggerInstance{
			ti,
		},
	}

	mStatusWriter.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Status").Return(mStatusWriter).Maybe()
	mClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		arg := args.Get(2).(*v1alpha1.TriggerInstance)
		ti.DeepCopyInto(arg)
	}).Maybe()
	mClient.On("List", mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
		arg := args.Get(1).(*v1alpha1.TriggerInstanceList)
		tiList.DeepCopyInto(arg)
	}).Maybe()
	mClientWithErr.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(errors.NewNotFound(schema.GroupResource{}, "TI")).Maybe()

	t.Run("AddOrUpdateEventHndlNormal", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClient,
			once:       sync.Once{},
			initedFlag: false,
		}
		assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), r.AddOrUpdateEventHndl(ctx, req))
	})

	t.Run("AddOrUpdateEventHndlFail", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClientWithErr,
			once:       sync.Once{},
			initedFlag: false,
		}
		assert.Error(t, fmt.Errorf("TestError"), r.AddOrUpdateEventHndl(ctx, req))
	})

	t.Run("ReconcileNormal", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClient,
			once:       sync.Once{},
			initedFlag: false,
		}
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		_, err := r.Reconcile(ctx, req)
		assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), err)
	})

	t.Run("ReconcileNotFound", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClientWithErr,
			once:       sync.Once{},
			initedFlag: false,
		}
		lease.SetMeLeader()
		defer func() { lease.SetMeWithoutStatus() }()
		_, err := r.Reconcile(ctx, req)
		assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), err)
	})

	t.Run("ReconcileIamLeader", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClientWithErr,
			once:       sync.Once{},
			initedFlag: false,
		}
		lease.SetMeSlave()
		defer func() { lease.SetMeWithoutStatus() }()
		_, err := r.Reconcile(ctx, req)
		assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), err)
	})

	t.Run("SetupWithManager", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClient,
			once:       sync.Once{},
			initedFlag: false,
		}
		mng, _ := controllerruntime.NewManager(&rest.Config{}, controllerruntime.Options{})
		assert.Error(t, fmt.Errorf("must provide a non-nil Manager"), r.SetupWithManager(mng))
	})

	//t.Run("Init", func(t *testing.T) {
	//	r := &TriggerInstanceReconciler{
	//		Client:     mClient,
	//		once:       sync.Once{},
	//		initedFlag: false,
	//	}
	//	IamLeader = false
	//	assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), r.Init(ctx))
	//	IamLeader = true
	//	assert.Error(t, fmt.Errorf("channel factory_create_request_event not found"), r.Init(ctx))
	//})

	t.Run("manageEvents", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			Client:     mClient,
			once:       sync.Once{},
			initedFlag: false,
		}
		p := r.manageEvents()
		ti2 := ti.DeepCopy()
		ti2.Generation = 1
		t1 := p.Update(event.UpdateEvent{
			ObjectOld: &ti,
			ObjectNew: ti2,
		})
		t2 := p.Create(event.CreateEvent{Object: &ti})
		t3 := p.Delete(event.DeleteEvent{
			Object:             &ti,
			DeleteStateUnknown: false,
		})
		assert.True(t, t1 && t2 && t3)
	})

}

func TestTriggerInstanceReconciler_HealthCheck(t *testing.T) {
	t.Run("test", func(t *testing.T) {
		r := &TriggerInstanceReconciler{
			initedFlag: false,
		}
		assert.Equalf(t, false, r.HealthCheck(), "HealthCheck()")
	})
}

func TestHandleUnhandledTIs(t *testing.T) {
	KbltCreator = "true"
	t.Run("i am not leader", func(t *testing.T) {
		lease.SetMeSlave()
		HandleUnhandledTIs("")
	})

	t.Run("error getting tis", func(t *testing.T) {
		lease.SetMeLeader()
		trigMock := mocks2.NewTriggerInstanceInterface(t)
		trigMock.On("List", mock.Anything, mock.Anything, mock.Anything).Return(&v1alpha1.TriggerInstanceList{}, errors2.New("err")).Once()

		cliMock := mocks2.NewV1Alpha1Interface(t)
		cliMock.On("TriggerInstance", mock.Anything).Return(trigMock).Once()

		Cl = cliMock

		HandleUnhandledTIs("")
	})

	t.Run("no unhandled tis", func(t *testing.T) {
		lease.SetMeLeader()
		trigMock := mocks2.NewTriggerInstanceInterface(t)
		trigMock.On("List", mock.Anything, mock.Anything, mock.Anything).
			Return(&v1alpha1.TriggerInstanceList{Items: []v1alpha1.TriggerInstance{
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: []v1alpha1.ResourceStatus{{}}},
				},
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: []v1alpha1.ResourceStatus{{}}},
				},
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: []v1alpha1.ResourceStatus{{}}},
				},
			}}, nil).Once()

		cliMock := mocks2.NewV1Alpha1Interface(t)
		cliMock.On("TriggerInstance", mock.Anything).Return(trigMock).Once()

		Cl = cliMock

		HandleUnhandledTIs("")
	})

	t.Run("ok", func(t *testing.T) {
		lease.SetMeLeader()
		trigMock := mocks2.NewTriggerInstanceInterface(t)
		trigMock.On("List", mock.Anything, mock.Anything, mock.Anything).
			Return(&v1alpha1.TriggerInstanceList{Items: []v1alpha1.TriggerInstance{
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: nil},
				},
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: nil},
				},
				{
					TypeMeta:   metav1.TypeMeta{},
					ObjectMeta: metav1.ObjectMeta{},
					Spec:       v1alpha1.TriggerInstanceSpec{},
					Status:     v1alpha1.TriggerInstanceStatus{ResourceStatus: []v1alpha1.ResourceStatus{{}}},
				},
			}}, nil).Once()

		cliMock := mocks2.NewV1Alpha1Interface(t)
		cliMock.On("TriggerInstance", mock.Anything).Return(trigMock).Once()

		Cl = cliMock

		HandleUnhandledTIs("")
	})
}
